package com.reebake.ideal.security.oauth2.config;

import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.RSA;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.reebake.ideal.crypto.core.SecretKeyService;
import com.reebake.ideal.crypto.entity.SecretKeyEntity;
import com.reebake.ideal.security.oauth2.properties.OAuth2ServerProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.List;

@Configuration
@Slf4j
public class OAuth2AuthorizationServerConfig {
	
	@Autowired
	private OAuth2ServerProperties oAuth2ServerProperties;
	@Autowired
	private SecretKeyService secretKeyService;

	@Bean
	SecurityFilterChain oauth2AuthorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
	    log.info("启动SecurityFilterChain: oauth2-authorization-server");
	    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
	    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
	        // 开启OpenID Connect 1.0协议相关端点
	        .oidc(Customizer.withDefaults())
	        // 设置自定义用户确认授权页
	        .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint.consentPage(oAuth2ServerProperties.getConsentPage()));
	    
	    http
        // 当未登录时访问认证端点时重定向至login页面
        .exceptionHandling((exceptions) -> exceptions
            .defaultAuthenticationEntryPointFor(
            new LoginUrlAuthenticationEntryPoint(oAuth2ServerProperties.getLoginFormUrl()),
            new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
            )
        )
        // 处理使用access token访问用户信息端点和客户端注册端点
        .oauth2ResourceServer((resourceServer) -> resourceServer
        .jwt(Customizer.withDefaults()));

        http.csrf(csrf -> csrf.disable());
        http.cors(Customizer.withDefaults());
	    
	    return http.build();
	}

	/**
	 * 配置oauth2的授权管理服务
	 * @param jdbcTemplate
	 * @param registeredClientRepository
	 * @return
	 */
	@Bean
	OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
		JdbcOAuth2AuthorizationService oauth2AuthorizationService = new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
	    
	    JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper authorizationRowMapper =
                new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(
                registeredClientRepository);
        authorizationRowMapper.setLobHandler(new DefaultLobHandler());
 
        ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
        List<com.fasterxml.jackson.databind.Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModules(securityModules);
        objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
        authorizationRowMapper.setObjectMapper(objectMapper);
 
        oauth2AuthorizationService.setAuthorizationRowMapper(authorizationRowMapper);

        return oauth2AuthorizationService;
	}

	/**
	 * 配置授权确认管理服务
	 *
	 * @param jdbcTemplate
	 * @param registeredClientRepository
	 * @return
	 */
	@Bean
	OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
	    return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
	}

	/**
	 * 添加认证服务器配置，设置jwt签发者、默认端点请求地址等
	 *
	 * @return AuthorizationServerSettings
	 */
	@Bean
	AuthorizationServerSettings authorizationServerSettings() {
	    return AuthorizationServerSettings.builder().build();
	}

	/**
	 * 配置客户端Repository
	 *
	 * @param jdbcTemplate
	 * @return
	 */
	@Bean
	RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
	    return new JdbcRegisteredClientRepository(jdbcTemplate);
	}
	
	/**
	 * 配置jwk源，使用非对称加密，公开用于检索匹配指定选择器的JWK的方法
	 *
	 * @return JWKSource
	 */
	@Bean
	JWKSource<SecurityContext> jwkSource() {
		SecretKeyEntity secretKey = secretKeyService.queryRsa();
		RSA rsa = SecureUtil.rsa(secretKey.getPrivateKey(), secretKey.getPublicKey());
	    RSAPublicKey publicKey = (RSAPublicKey) rsa.getPublicKey();
	    RSAPrivateKey privateKey = (RSAPrivateKey) rsa.getPrivateKey();
	    RSAKey rsaKey = new RSAKey.Builder(publicKey)
	        .privateKey(privateKey)
	        .keyID("default")
	        .build();
	    JWKSet jwkSet = new JWKSet(rsaKey);
	    return new ImmutableJWKSet<>(jwkSet);
	}

}
